package com.cevent.community.app.common.config;/**
 * Created by Cevent on 2021/9/14.
 */

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.cevent.community.app.common.utils.UUID8Util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertiesPersister;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;

/**
 * @author cevent
 * @description 全局AOP日志捕获
 * aspect orient programming面向切面的统一日志管理配置
 * component：声明spring需要扫描的组件
 * @date 2021/9/14 17:41
 */
@Aspect
@Component
public class AOPLog {
    public static final Logger LOG = LoggerFactory.getLogger(AOPLog.class);

    //1.定义切面:所有包下*Controller的方法
    @Pointcut("execution(public * com.cevent.community.app.*.controller..*Controller.*(..))")
    public void controllerPointCutAOP() {
    }

    //2.切点开始之前执行
    @Before("controllerPointCutAOP()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // MDC日志框架slf4j内置变量配置
        MDC.put("UUID", UUID8Util.getUUID());
        //servlet请求(请求头)--> 打印请求日志
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取http请求
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
        //获取签名信息
        Signature signature = joinPoint.getSignature();
        //获取签名后，获取void方法头
        String signatureName = signature.getName();
        //打印业务操作
        String serviceOperation = "";
        //请求路径包含约定要求，get：方法首位必须是list/get ...
        if (signatureName.contains("get") || signatureName.contains("list") || signatureName.contains("query") || signatureName.contains("find")) {
            serviceOperation = "查询";
        } else if (signatureName.contains("save") || signatureName.contains("add") || signatureName.contains("insert") || signatureName.contains("edit") || signatureName.contains("update")) {
            serviceOperation = "保存";
        } else if (signatureName.contains("delete") || signatureName.contains("del") || signatureName.contains("remove")) {
            serviceOperation = "删除";
        } else if (signatureName.contains("upload") || signatureName.contains("oss") || signatureName.contains("vod") || signatureName.contains("append")) {
            serviceOperation = "文件上传";
        } else {
            serviceOperation = "更新其他操作";
        }

        //3.反射：签名声明 获取业务名称（签名声明类型:BUSINESS_NAME）
        Class clazz = signature.getDeclaringType();
        Field field;
        String serverName = "";
        //获取到的签名反射域不为空
        try {
            field = clazz.getField("BUSINESS_NAME");
            if (!StringUtils.isEmpty(field)) {
                serverName = (String) field.get(clazz);
            }
        } catch (SecurityException e) {
            LOG.info("没有获取到服务名称，默认 [ BUSINESS_NAME ] : ", e);
        }

        //4.打印请求
        LOG.info("---BUSINESS【{}】 {} 开始 ---", serverName, serviceOperation);
        LOG.info("请求路径：{} {}", httpServletRequest.getRequestURL().toString(), httpServletRequest.getMethod());
        LOG.info("类名：{} 方法：{}", signature.getDeclaringTypeName(), signatureName);
        LOG.info("远程IP地址：{}", httpServletRequest.getRemoteAddr());

        //5.打印请求参数
        Object[] args = joinPoint.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            //去除的请求参数类型request、response、multipartFile文件类型
            if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }

        //6.排除字段，敏感字段(身份证/手机号等)或太长的字段(图片会转为base64长文本)不显示，定义碎片拦截shard
        String[] excludeProperties = {"shard"};
        //引入AspectJ 阿里图片转换fastjson，进行图片拦截，这里需要多等一会儿alt+enter
        PropertyPreFilters preFilters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter preFilter = preFilters.addFilter();
        preFilter.addExcludes(excludeProperties);
        //为空不打印，但是类图片等长字段会打印（引用存入的arguments请求类型）
        LOG.info("preFilters过滤器，排除不打印字段excludes，请求参数：{}", JSONObject.toJSONString(arguments, preFilter));
    }

    //环绕通知：打印返回信息，进程切点
    @Around("controllerPointCutAOP()")
    public Object returnAroundAopMessage(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //打印时间：结束-开始执行时间
        long start = System.currentTimeMillis();
        //执行业务proceed代码进程记录，结束后return
        Object result = proceedingJoinPoint.proceed();
        //排除字段，敏感字段或太长的字段不显示，前端进行datForam参数转换param时，将file转为base64，进行shard:传输，太长需去掉打印
        String[] excludeProperties = {"password", "shard"};
        PropertyPreFilters preFilters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter simplePropertyPreFilter = preFilters.addFilter();
        simplePropertyPreFilter.addExcludes(excludeProperties);
        LOG.info("@Around-AOP日志环绕返回结果：{}", JSONObject.toJSONString(result, simplePropertyPreFilter));
        LOG.info("----------------程序进程耗时：{} ms------------------", System.currentTimeMillis() - start);

        return result;
    }
}
